/**
 * view.js
 * @author Ulyan Sobin
 * @description View modules
 */

"use strict";

const fs = require("fs");
const { WebHttpHandler } = require("./cube-web/http/WebHttpHandler");
const { WebHttpClient } = require("./cube-web/http/WebHttpClient");
const { WebHttpRequest } = require("./cube-web/http/WebHttpRequest");
const { cube_info } = require(".");

const DEFAULT_HEAD = {
	"Content-Type": "text/html; charset=utf-8"
};

const SERVER_INFO = `${cube_info.name}/${cube_info.version}`;

class View extends WebHttpHandler {

	/**
	 * Constructor of `View`
	 */
	constructor () {
		super((client) => {
			let view = this.getView(client.request);
			let head = Object.assign(DEFAULT_HEAD, view.head);
			client.response.press(view.status, head, view.body);
		});
	}

	/**
	 * @abstract
	 * @param {WebHttpRequest} request
	 * @return {{
	 *    status: number,
	 *    head: Object.<string, string>?,
	 *    body: string
	 * }}
	 */
	getView(request) {
		throw new Error("Function not implemented");
	}

	/**
	 * Publish the view to a client.
	 * @param {WebHttpClient} client 
	 */
	publish(client) {
		this.resolve(client);
	}
}


class ActivityView extends View {

	/**
	 * @constant
	 * @private
	 * @type { string }
	 */
	template;

	/**
	 * @private
	 * @type { function (): any }
	 */
	pkg_func;

	/**
	 * Constructor of `ActivityView`
	 * @param { string } template_path 
	 * @param { function (WebHttpRequest): any } pkg_func
	 */
	constructor (template_path, pkg_func) {
		super();
		this.template = fs.readFileSync(template_path).toString();
		this.pkg_func = pkg_func;
	}

	/**
	 * @override
	 * @param {WebHttpRequest} request
	 * @return {{
	 *    status: number,
	 *    head: Object.<string, string>?,
	 *    body: string
	 * }}
	 */
	getView(request) {
		function preProcess(text, view) {
			var echoText = "";
			var line = 1;
			var column = 1;
			function errorPage(info, detail) {
				return View.errorPage(500, `${info}: ${detail}`).content;
			}
			function echo(str) {
				echoText += str;
			}
			var i = 0;
			var ch = '0';
			function nextCh() {
				if (ch == '\n') {
					line++;
					column = 0;
				}
				else {
					column++;
				}
				i++;
			}
			var R = "";
			var innerScript = "";
			const len = text.length;
			for (i = 0; i < len; nextCh()) {
				ch = text[i];
				if (ch != '<') {
					R += ch;
					continue;
				}
				else {
					echoText = "";
					innerScript = "";
					if (i + 1 >= len) {
						R += '<';
						break;
					}
					nextCh();
					if (text[i] != '$') {
						R += "<" + text[i];
						continue;
					}
					nextCh();
					while (i < len) {
						ch = text[i];
						if (ch == '$') {
							if (i + 1 >= len) {
								return errorPage("Incorrect end",
									"At (" + line + ", " + column + "): " +
									"<$" + innerScript + "$_");
							}
							nextCh();
							if (text[i] == '>') {
								// script end
								nextCh();
								break;
							}
							innerScript += '$';
							ch = text[i];
						}
						innerScript += ch;
						nextCh();
					}
					if (i >= len && text[i - 1] != '>') {
						return errorPage("Incorrect end",
							"At (" + line + ", " + column + "): " + "<$" + innerScript + "_");
					}
					try {
						eval(innerScript);
						innerScript = "";
					}
					catch (err) {
						return errorPage("Script error",
							"At (" + line + ", " + column + "): " + err.toString());
					}
					R += echoText + text[i];
					echoText = "";
				}
			}
			return R;
		}
		let body = preProcess(this.template, this.pkg_func(request));
		body = new TextEncoder().encode(body);
		return {
			status: 200,
			head: {"Content-Length": body.length.toString()},
			body,
		};
	}
}


class ErrorView extends View {

	/** @type {number} */
	code;

	/** @type {string} */
	message;

	/**
	 * Constructor of `ErrorView`
	 * @param {number} err_code 
	 * @param {string} message 
	 */
	constructor (code, message) {
		super();
		this.code = code;
		this.message = message;
	}

	/**
	 * @override
	 * @param {WebHttpRequest} request
	 * @return {{
	*    status: number,
	*    head: Object.<string, string>?,
	*    body: string
	* }}
	 */
	getView(request) {
		let html = 
		/* Template start */
`<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <title>${this.code} - ERROR</title>
</head>
<body>
  <h1 style="text-align:center; margin-top:1em;">${this.code}</h1>
  <hr>
  <p style="text-align:center; margin-top:1em;">${this.message}</p>
  <p style="text-align:center; font-family:Monospace;">${SERVER_INFO}</p>
</body>
<html>`;
		let body = new TextEncoder().encode(html);
		/* Template end */
		return {
			status: this.code,
			head: {"Content-Length": body.length.toString()},
			body,
		};
	}
}

/**
 * Redirect http page to `url`
 * @param {string} url 
 */
function redirectTo(url) {
	const html = 
`<!DOCTYPE html>
<html>
<head>
<script>document.location.href="${url}"</script>
</head>
<body></body>
</html>`;
	const view = new View();
	view.getView = () => {
		return {
			status: 200,
			head: {},
			body: html
		};
	}
	return view;
}


module.exports.View = View;
module.exports.ActivityView = ActivityView;
module.exports.ErrorView = ErrorView;
module.exports.redirectTo = redirectTo;